package cc.shacocloud.mirage.restful;

import cc.shacocloud.mirage.bean.BeanFactory;
import cc.shacocloud.mirage.utils.ClassUtil;
import cc.shacocloud.mirage.utils.MethodParameter;
import cc.shacocloud.mirage.utils.ResolvableType;
import cc.shacocloud.mirage.utils.Utils;
import cc.shacocloud.mirage.utils.annotation.AnnotatedElementUtils;
import cc.shacocloud.mirage.utils.charSequence.StrUtil;
import cc.shacocloud.mirage.utils.collection.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 封装处理程序的方法和相关信息，提供了方法参数，方法返回值，方法注解等方便
 */
public class HandlerMethod {
    
    @Nullable
    protected final BeanFactory beanFactory;
    private final Object bean;
    private final Class<?> beanType;
    
    private final Method method;
    private final String description;
    protected MethodParameter[] parameters;
    @Nullable
    private HandlerMethod resolvedFromHandlerMethod;
    @Nullable
    private volatile List<Annotation[][]> interfaceParameterAnnotations;
    
    /**
     * 从bean实例和方法创建实例
     */
    public HandlerMethod(@NotNull Object bean, @NotNull Method method) {
        this.bean = bean;
        this.beanFactory = null;
        this.beanType = bean.getClass();
        this.method = method;
        this.parameters = initMethodParameters();
        this.description = Utils.methodDescription(this.beanType, this.method);
    }
    
    /**
     * 从Bean实例，方法名称和参数类型创建实例。
     *
     * @throws NoSuchMethodException 当找不到方法时抛出该例外
     */
    public HandlerMethod(@NotNull Object bean, @NotNull String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
        this.bean = bean;
        this.beanFactory = null;
        this.beanType = bean.getClass();
        this.method = bean.getClass().getMethod(methodName, parameterTypes);
        this.parameters = initMethodParameters();
        this.description = Utils.methodDescription(this.beanType, this.method);
    }
    
    /**
     * 从一个bean名，方法，以及创建一个实例的BeanFactory
     * 该方法 {@link #createWithResolvedBean()} 可以随后用于重新创建{@link HandlerMethod}与初始化的 bean
     */
    public HandlerMethod(@NotNull String beanName, @NotNull BeanFactory beanFactory, @NotNull Method method) {
        
        this.bean = beanName;
        this.beanFactory = beanFactory;
        this.beanType = beanFactory.getBeanType(beanName);
        this.method = method;
        this.parameters = initMethodParameters();
        this.description = Utils.methodDescription(this.beanType, this.method);
    }
    
    /**
     * 复制用于子类的构造函数
     */
    protected HandlerMethod(@NotNull HandlerMethod handlerMethod) {
        this.bean = handlerMethod.bean;
        this.beanFactory = handlerMethod.beanFactory;
        this.beanType = handlerMethod.beanType;
        this.method = handlerMethod.method;
        this.parameters = handlerMethod.parameters;
        this.description = handlerMethod.description;
        this.resolvedFromHandlerMethod = handlerMethod.resolvedFromHandlerMethod;
    }
    
    /**
     * 使用解析后的处理程序重新创建HandlerMethod
     */
    private HandlerMethod(@NotNull HandlerMethod handlerMethod, Object handler) {
        this.bean = handler;
        this.beanFactory = handlerMethod.beanFactory;
        this.beanType = handlerMethod.beanType;
        this.method = handlerMethod.method;
        this.parameters = handlerMethod.parameters;
        this.resolvedFromHandlerMethod = handlerMethod;
        this.description = handlerMethod.description;
    }
    
    protected MethodParameter[] initMethodParameters() {
        int count = this.method.getParameterCount();
        MethodParameter[] result = new MethodParameter[count];
        for (int i = 0; i < count; i++) {
            result[i] = buildMethodParameter(i);
        }
        return result;
    }
    
    /**
     * 构建方法指定索引值的参数，下标从 0 开始，一般来说值为 -1 的为方法的返回值类型
     */
    protected MethodParameter buildMethodParameter(int index) {
        return new HandlerMethodParameter(this, index);
    }
    
    /**
     * 返回此处理程序方法的bean
     */
    public Object getBean() {
        return this.bean;
    }
    
    /**
     * 返回此处理程序方法的方法
     */
    public Method getMethod() {
        return this.method;
    }
    
    /**
     * 此方法返回的处理程序，该处理方法的类型。
     */
    public Class<?> getBeanType() {
        return this.beanType;
    }
    
    /**
     * 返回此处理程序方法的方法参数
     */
    public MethodParameter[] getMethodParameters() {
        return this.parameters;
    }
    
    /**
     * 返回HandlerMethod返回类型
     */
    public MethodParameter getReturnType() {
        return buildMethodParameter(-1);
    }
    
    /**
     * 返回实际的返回值类型
     */
    public MethodParameter getReturnValueType(@Nullable Object returnValue) {
        return new ReturnValueMethodParameter(this, returnValue);
    }
    
    /**
     * 如果该方法返回类型为void返回true 否则为false。
     */
    public boolean isVoid() {
        return Void.TYPE.equals(getReturnType().getParameterType());
    }
    
    /**
     * @see AnnotatedElementUtils#getAnnotation
     */
    @Nullable
    public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
        return AnnotatedElementUtils.getAnnotation(this.method, annotationType);
    }
    
    /**
     * @see AnnotatedElementUtils#hasAnnotation
     */
    public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) {
        return AnnotatedElementUtils.hasAnnotation(this.method, annotationType);
    }
    
    /**
     * 返回从{@link HandlerMethod}解析得到的 {@link #createWithResolvedBean()}.
     */
    @Nullable
    public HandlerMethod getResolvedFromHandlerMethod() {
        return this.resolvedFromHandlerMethod;
    }
    
    /**
     * 如果所提供的实例包含一个bean的名称，而不是一个对象实例，一个bean之前，名称解析{@link HandlerMethod}创建并返回
     */
    public HandlerMethod createWithResolvedBean() {
        Object handler = this.bean;
        if (handler instanceof String) {
            Objects.requireNonNull(this.beanFactory, "没有BeanFactory无法解析Bean名称");
            handler = this.beanFactory.getBean((String) handler);
        }
        return new HandlerMethod(this, handler);
    }
    
    /**
     * 返回此处理程序方法的简短表示形式，以用于日志消息。
     */
    public String getShortLogMessage() {
        return getBeanType().getName() + "#" + this.method.getName() +
                "[" + this.method.getParameterCount() + " args]";
    }
    
    
    public List<Annotation[][]> getInterfaceParameterAnnotations() {
        List<Annotation[][]> parameterAnnotations = this.interfaceParameterAnnotations;
        if (parameterAnnotations == null) {
            parameterAnnotations = new ArrayList<>();
            for (Class<?> ifc : ClassUtil.getAllInterfacesForClassAsSet(this.method.getDeclaringClass())) {
                for (Method candidate : ifc.getMethods()) {
                    if (isOverrideFor(candidate)) {
                        parameterAnnotations.add(candidate.getParameterAnnotations());
                    }
                }
            }
            this.interfaceParameterAnnotations = parameterAnnotations;
        }
        return parameterAnnotations;
    }
    
    private boolean isOverrideFor(@NotNull Method candidate) {
        if (!candidate.getName().equals(this.method.getName()) ||
                candidate.getParameterCount() != this.method.getParameterCount()) {
            return false;
        }
        Class<?>[] paramTypes = this.method.getParameterTypes();
        if (Arrays.equals(candidate.getParameterTypes(), paramTypes)) {
            return true;
        }
        for (int i = 0; i < paramTypes.length; i++) {
            if (paramTypes[i] != ResolvableType.forMethodParameter(candidate, i, this.method.getDeclaringClass()).resolve()) {
                return false;
            }
        }
        return true;
    }
    
    
    @Override
    public boolean equals(@Nullable Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof HandlerMethod)) {
            return false;
        }
        HandlerMethod otherMethod = (HandlerMethod) other;
        return (this.bean.equals(otherMethod.bean) && this.method.equals(otherMethod.method));
    }
    
    @Override
    public int hashCode() {
        return (this.bean.hashCode() * 31 + this.method.hashCode());
    }
    
    /**
     * 获取处理器方法描述
     */
    public String getDescription() {
        return description;
    }
    
    @Override
    public String toString() {
        return getDescription();
    }
    
    
    // ------------------------ InvocableHandlerMethod 支持的方法
    
    @Nullable
    protected Object findProvidedArgument(MethodParameter parameter, @Nullable Object... providedArgs) {
        if (ArrayUtil.isNotEmpty(providedArgs)) {
            for (Object providedArg : providedArgs) {
                if (parameter.getParameterType().isInstance(providedArg)) {
                    return providedArg;
                }
            }
        }
        return null;
    }
    
    protected String formatArgumentError(@NotNull MethodParameter param, String message) {
        return "无法解析方法 '" + Utils.methodDescription(param.getDeclaringClass(), Objects.requireNonNull(param.getMethod()))
                + "' 参数索引[" + param.getParameterIndex() + "] 类型[" + param.getParameterType() + "]"
                + (StrUtil.isNotBlank(message) ? ("： " + message) : "");
    }
    
    /**
     * 断言目标bean类是在给定的方法被声明的类的实例
     */
    protected void assertTargetBean(@NotNull Method method, @NotNull Object targetBean, Object[] args) {
        Class<?> methodDeclaringClass = method.getDeclaringClass();
        Class<?> targetBeanClass = targetBean.getClass();
        if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) {
            String text = "映射的处理程序方法类 '" + methodDeclaringClass.getName() + "' 不是实际控制器bean类的实例 '" +
                    targetBeanClass.getName() + "'。 如果控制器需要代理 (e.g. due to @Transactional)，请使用基于代理的类。";
            throw new IllegalStateException(formatInvokeError(text, args));
        }
    }
    
    protected String formatInvokeError(String text, Object @NotNull [] args) {
        String formattedArgs = IntStream.range(0, args.length)
                .mapToObj(i -> (args[i] != null ?
                        "下标[" + i + "] [目标类型=" + args[i].getClass().getName() + "] [值=" + args[i] + "]" :
                        "下标[" + i + "] [null]"))
                .collect(Collectors.joining(",\n", " ", " "));
        return text + "\n" +
                "控制器 [" + getBeanType().getName() + "]\n" +
                "方法 [" + getMethod().toGenericString() + "] \n" +
                "参数:\n" + formattedArgs;
    }
    
    
    /**
     * 具有 HandlerMethod 特定行为的 MethodParameter
     */
    public static class HandlerMethodParameter extends MethodParameter {
        
        private final HandlerMethod handlerMethod;
        @Nullable
        private volatile Annotation[] combinedAnnotations;
        
        public HandlerMethodParameter(@NotNull HandlerMethod handlerMethod, int index) {
            super(handlerMethod.method, index);
            this.handlerMethod = handlerMethod;
        }
        
        public HandlerMethodParameter(HandlerMethodParameter original) {
            super(original);
            this.handlerMethod = original.handlerMethod;
        }
        
        @NotNull
        @Override
        public Class<?> getContainingClass() {
            return handlerMethod.getBeanType();
        }
        
        @Override
        public <T extends Annotation> T getMethodAnnotation(@NotNull Class<T> annotationType) {
            return handlerMethod.getMethodAnnotation(annotationType);
        }
        
        @Override
        public <T extends Annotation> boolean hasMethodAnnotation(@NotNull Class<T> annotationType) {
            return handlerMethod.hasMethodAnnotation(annotationType);
        }
        
        @NotNull
        @Override
        public Annotation[] getParameterAnnotations() {
            Annotation[] anns = this.combinedAnnotations;
            if (anns == null) {
                anns = super.getParameterAnnotations();
                int index = getParameterIndex();
                if (index >= 0) {
                    for (Annotation[][] ifcAnns : handlerMethod.getInterfaceParameterAnnotations()) {
                        if (index < ifcAnns.length) {
                            Annotation[] paramAnns = ifcAnns[index];
                            if (paramAnns.length > 0) {
                                List<Annotation> merged = new ArrayList<>(anns.length + paramAnns.length);
                                merged.addAll(Arrays.asList(anns));
                                for (Annotation paramAnn : paramAnns) {
                                    boolean existingType = false;
                                    for (Annotation ann : anns) {
                                        if (ann.annotationType() == paramAnn.annotationType()) {
                                            existingType = true;
                                            break;
                                        }
                                    }
                                    if (!existingType) {
                                        merged.add(adaptAnnotation(paramAnn));
                                    }
                                }
                                anns = merged.toArray(new Annotation[0]);
                            }
                        }
                    }
                }
                this.combinedAnnotations = anns;
            }
            return anns;
        }
        
        @NotNull
        @Override
        public HandlerMethodParameter clone() {
            return new HandlerMethodParameter(this);
        }
    }
    
    
    /**
     * 基于实际返回值的HandlerMethod返回类型的MethodParameter
     */
    private static class ReturnValueMethodParameter extends HandlerMethodParameter {
        
        @Nullable
        private final Object returnValue;
        
        public ReturnValueMethodParameter(HandlerMethod handlerMethod, @Nullable Object returnValue) {
            super(handlerMethod, -1);
            this.returnValue = returnValue;
        }
        
        protected ReturnValueMethodParameter(ReturnValueMethodParameter original) {
            super(original);
            this.returnValue = original.returnValue;
        }
        
        @Override
        public Class<?> getParameterType() {
            return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType());
        }
        
        @Override
        public @NotNull ReturnValueMethodParameter clone() {
            return new ReturnValueMethodParameter(this);
        }
    }
    
}
